<template>
  <form class="page page-browser-password">
    <h4 class="text-center">浏览器密码查看器</h4>
    <fieldset class="page-header">
      <legend>参数配置</legend>
      <div class="row-flex" align="center" style="margin-top:0">
        <div class="row-flex-prefix">Chrome</div>
        <div class="row-flex-main"><input class="form-control form-control-sm" type="text" v-model="chromePath" readonly /></div>
        <div class="row-flex-suffix"><x-button size="small" @click="setPath('chrome')">选择...</x-button></div>
      </div>
      <div class="row-flex" align="center">
        <div class="row-flex-prefix">Opera</div>
        <div class="row-flex-main"><input class="form-control form-control-sm" type="text" v-model="operaPath" readonly /></div>
        <div class="row-flex-suffix"><x-button size="small" @click="setPath('opera')">选择...</x-button></div>
      </div>
      <!-- <div class="row-flex" align="center">
        <div class="row-flex-prefix">Firefox</div>
        <div class="row-flex-main"><input class="form-control form-control-sm" type="text" v-model="firefoxPath" readonly /></div>
        <div class="row-flex-suffix"><x-button size="small" @click="setPath('firefox')">选择...</x-button></div>
      </div> -->
    </fieldset>
    <fieldset class="page-body">
      <legend>数据列表</legend>
      <div class="table-header" :style="thStyles">
        <table>
          <tbody>
            <tr>
              <th v-for="(v,i) in tableHeaders" :key="`th_${i}`" :style="{width: v.width}">{{v.label}}</th>
            </tr>
          </tbody>
        </table>
      </div>
      <div class="table-body" @drop.prevent="filesDrop" @dragover.prevent>
        <table ref="table">
          <tbody>
            <tr v-for="(v,i) in dataList" :key="`tr_${i}`">
              <template v-for="(item,index) in tableHeaders">
                <td :style="{width: item.width}" :key="`td_${i}_${index}`">
                  <span v-if="item.value==='date_created'">{{v[item.value] | formatDate}}</span>
                  <span v-else>{{v[item.value] || ''}}</span>
                </td>
              </template>
            </tr>
            <tr v-if="dataList.length===0" class="nodata-tr"><td>暂无数据</td></tr>
          </tbody>
        </table>
      </div>
    </fieldset>
  </form>
</template>

<script>
const path = global.require('path')
const fs = global.require('fs')
const {remote} = global.require('electron')
const {dialog} = remote
const selfWindow= remote.getCurrentWindow()
let {debounce, formatDate}  = global.$jstools || {}
// 调用C++动态库破解密码
const sqlite3 = global.sqlite3
const ref = global.ref
const ffi = global.ffi
const Struct = global.Struct(ref)
const DATA_BLOB = Struct({
  cbData: ref.types.uint32,
  pbData: ref.refType(ref.types.byte)
})
const PDATA_BLOB = new ref.refType(DATA_BLOB)
const Crypto = new ffi.Library('crypt32', {
  CryptUnprotectData: ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]],
  CryptProtectData : ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]]
})

export default {
  name: 'browser-password',
  data () {
    return {
      firefoxPath: '',
      firefoxDataList: [],
      firefoxDb: null,
      firefoxJson: [],
      operaPath: '',
      operaDataList: [],
      operaDb: null,
      chromePath: '',
      chromeDataList: [],
      chromeDb: null,
      dbDict: {
        chrome: {
          schema: 'logins', origin_url: 'origin_url', action_url: 'action_url',
          username_element: 'username_element',password_element: 'password_element',
          username_value: 'username_value', password_value: 'password_value', date_created: 'date_created',
        },
        opera: {
          schema: 'logins', origin_url: 'origin_url', action_url: 'action_url',
          username_element: 'username_element',password_element: 'password_element',
          username_value: 'username_value', password_value: 'password_value', date_created: 'date_created',
        },
        // firefox: {
        //   schema: 'logins', origin_url: 'hostname', action_url: 'formSubmitURL',
        //   username_element: 'usernameField',password_element: 'passwordField',
        //   username_value: 'encryptedUsername', password_value: 'encryptedPassword', date_created: 'timeCreated',
        // },
      },
      tableHeaders: [
        {label: '源URL', value: 'origin_url', width: '20%'},
        {label: '操作URL', value: 'action_url', width: '20%'},
        {label: '用户名字段', value: 'username_element', width: '10%'},
        {label: '密码字段', value: 'password_element', width: '10%'},
        {label: '用户名', value: 'username_value', width: '10%'},
        {label: '密码', value: 'password_value', width: '10%'},
        {label: '创建时间', value: 'date_created', width: '10%'},
        {label: '浏览器类别', value: 'browser', width: '10%'},
      ],
      thStyles: {},
      loadingCount: 0
    }
  },
  computed: {
    dataList () {
      return [
        ...this.chromeDataList,
        ...this.operaDataList,
        ...this.firefoxDataList,
      ]
    }
  },
  filters: {
    // 日期格式化
    formatDate (val) {
      let _val = val / 8275
      return formatDate(_val) || val
    }
  },
  mounted () {
    this.init()
  },
  methods: {
    // 设置文件路径
    setPath (browserName) {
      let filePaths = dialog.showOpenDialogSync(selfWindow, {
        title: '选择文件',
        buttonLabel:'确定',
        defaultPath: this.$sharedObject('desktopPath'),
        properties: ['openFile']
      })
      if (filePaths instanceof Array && filePaths.length > 0) {
        this[`${browserName}Path`] = filePaths[0]
        this.getSingleDataList(browserName)
      }
    },
    // 读取单个数据库文件
    getSingleDataList (browserName) {
      let fields = ['origin_url', 'action_url', 'username_element', 'password_element', 'username_value', 'password_value', 'date_created']
      let dbDictActive = this.dbDict[browserName]
      if (!dbDictActive) return
      if (!fs.existsSync(this[`${browserName}Path`])) return
      // 拷贝文件到临时目录
      let tempFilePath = path.join(this.$sharedObject('tempDataPath'), `xTools_${browserName}Db`)
      fs.copyFileSync(this[`${browserName}Path`], tempFilePath)
      if (browserName === 'firefox') {
        // fs.readFile(this.firefoxPath, 'utf8', (err, data) => {
        //   if (err) return
        //   this.firefoxJson = (this.$jstools.jsonParse(data)).logins || []
        // })
      } else if (['chrome', 'opera'].includes(browserName)) {
        this[`${browserName}Db`] = new sqlite3.Database(tempFilePath, (err) => {
          if (err) {
            this.$toast(`加载${browserName}数据库文件失败`)
            return
          }
          let fieldsStr = fields.map(v => dbDictActive[v]).join(', ')
          this[`${browserName}Db`].all(`SELECT ${fieldsStr} FROM ${dbDictActive.schema}`, (err, rows) => {
            if (err) {
              this.$toast(`查询${browserName}数据库失败`)
              return
            }
            this[`${browserName}DataList`] = (rows || []).map(v => {
              let result = {browser: browserName}
              fields.forEach(item => {
                result[item] = v[dbDictActive[item]]
                if (item === 'password_value') {
                  result[item] = this.decrypt(result[item])
                }
              })
              return result
            })
            this[`${browserName}Db`].close()
            this.checkScroll()
          })
        })
      }
    },
    // 解密
    decrypt (passwordValue) {
      let password = ''
      let dataBlobInput = new DATA_BLOB()
      dataBlobInput.pbData = passwordValue
      dataBlobInput.cbData = passwordValue.length
      let dataBlobOutput = ref.alloc(DATA_BLOB)
      let result = Crypto.CryptUnprotectData(dataBlobInput.ref(), null, null, null, null, 0, dataBlobOutput)
      if (result) {
        let outputDeref = dataBlobOutput.deref()
        password = ref.reinterpret(outputDeref.pbData, outputDeref.cbData, 0)
      }
      return password
    },
    getFirefoxPath () {
      let firefoxPath = ''
      let firefoxDir = path.join(this.$sharedObject('appDataPath'), './Mozilla/Firefox/Profiles/')
      if (!fs.existsSync(firefoxDir)) return
      let files = fs.readdirSync(firefoxDir) || []
      files.forEach(v => {
        if (/(.+)\.default$/i.test(v)) {
          firefoxPath = path.join(firefoxDir, v, 'logins.json')
        }
      })
      return firefoxPath
    },
    // 修正滚动条
    checkScroll: debounce(function () {
      this.$nextTick(() => {
        if (!this.$refs.table) return
        let tableHeight = this.$refs.table.clientHeight
        let tableParentHeight = this.$refs.table.parentNode.clientHeight
        if (tableHeight > tableParentHeight) {
          this.thStyles = {overflow: 'hidden scroll'}
        } else {
          this.thStyles = {overflow: 'hidden'}
        }
      })
    }),
    // 初始化
    init () {
      this.chromePath = path.join(this.$sharedObject('appDataPath'), '../Local/Google/Chrome/User Data/Default/Login Data')
      this.operaPath = path.join(this.$sharedObject('appDataPath'), './Opera Software/Opera Stable/Login Data')
      // this.firefoxPath = this.getFirefoxPath()
      let browsers = Object.keys(this.dbDict)
      browsers.forEach(v => {
        this.getSingleDataList(v)
      })
    }
  }
}
</script>

<style scoped lang="less">
@import "~@/assets/css/_mixins.less";
@import "~@/assets/css/_color.less";
.page-browser-password {
	display: flex;
	flex-direction: column;
  .row-flex-prefix {
    width: 4em;
    text-align: left;
  }
  fieldset {
    margin-top: 1em;
  }
  .page-header {
    fieldset {
      height: calc(100% - 1em);
    }
    .row-flex {
      margin-top: 1em;
      font-size: .85em;
    }
  }
	.page-body {
		height: 0;
		flex-grow: 1;
    .table-header,
    .table-body {
      table {
        width: 100%;
        border: 1px solid @borderGrey;
        tr {height: 2em;}
        th, td {
          border: 1px solid @borderGrey;
          text-align: center;
          vertical-align: middle;
        }
        .nodata-tr {
          height: 100%;
          font-size: 16px;
          color: #999;
        }
      }
    }
    .table-body {
      height: calc(100% - 4.5em);
      overflow: auto;
      table {
        min-height: 100%;
        font-size: .85em;
      }
    }
  }
}
</style>
